﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Text;

namespace Kang.ExtendMethod
{
    public enum DateCalcUnit
    {
        年,
        月,
        日,
        时,
        分,
        秒,
        毫秒,
    }

    public enum DateFormatLevel
    {
        年,
        月,
        日,
        时,
        分,
        秒,
        毫秒,
    }

    public static class DateTimeExtendMethod
    {
        private static readonly DateTime GMTTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

        /// <summary>
        /// 计算年龄
        /// </summary>
        /// <param name="birth">出生日期</param>
        /// <param name="date">计算时间</param>
        /// <returns>年龄</returns>
        public static int Age(this DateTime? birth, DateTime? date)
        {
            if (birth == null)
            {
                throw new ArgumentNullException(nameof(birth));
            }
            if (date == null)
            {
                date = DateTime.Now;
            }
            if (birth > date)
            {
                throw new ArgumentException("Date cannot be later than birth date");
            }
            int birthYear = birth.Value.Year;
            int birthMonth = birth.Value.Month;
            int birthDay = birth.Value.Month;
            bool isLastDayOfMonthBirth = birthDay == new DateTime(birthYear, birthMonth, 1).AddMonths(1).AddDays(-1).Day;

            int dateYear = date.Value.Year;
            int dateMonth = date.Value.Month;
            int dateDay = date.Value.Month;

            int age = dateYear - birthYear;
            if (age == 0)
            {
                return 0;
            }
            if (dateMonth == birthMonth)
            {
                bool isLastDayOfMonth = dateDay == new DateTime(dateYear, dateMonth, 1).AddMonths(1).AddDays(-1).Day;
                if ((false == isLastDayOfMonth || false == isLastDayOfMonthBirth) && dateMonth < birthMonth)
                {
                    // 如果生日在当月，但是未达到生日当天的日期，年龄减一
                    age--;
                }
            }
            else if(dateMonth < birthMonth)
            {
                age--;
            }
            return age;
        }

        /// <summary>
        /// 获取时间戳(秒)
        /// </summary>
        /// <param name="now">当前时间</param>
        /// <param name="flag">是否精确到秒：true 秒 ， false 毫秒</param>
        /// <returns></returns>
        public static long GetTimeStamp(this DateTime now)
        {
            var totalMilliseconds = (now.ToUniversalTime() - GMTTime).TotalMilliseconds;
            return Convert.ToInt64(totalMilliseconds) / 1000;
        }

        /// <summary>
        /// 获取时间戳(秒)
        /// </summary>
        /// <param name="now">当前时间</param>
        /// <param name="flag">是否精确到秒：true 秒 ， false 毫秒</param>
        /// <returns></returns>
        public static long GetTimeStamp(this DateTime? now)
        {
            if(now == null)
            {
                return 0;
            }
            return GetTimeStamp(now.Value);
        }

        /// <summary>
        /// 获取时间戳(毫秒)
        /// </summary>
        /// <param name="now">当前时间</param>
        /// <param name="flag">是否精确到秒：true 秒 ， false 毫秒</param>
        /// <returns></returns>
        public static long GetLongTimeStamp(this DateTime now)
        {
            var totalMilliseconds = (now.ToUniversalTime() - GMTTime).TotalMilliseconds;
            return Convert.ToInt64(totalMilliseconds);
        }

        /// <summary>
        /// 获取时间戳(毫秒)
        /// </summary>
        /// <param name="now">当前时间</param>
        /// <param name="flag">是否精确到秒：true 秒 ， false 毫秒</param>
        /// <returns></returns>
        public static long GetLongTimeStamp(this DateTime? now)
        {
            if(now == null)
            {
                return 0;
            }
            return GetLongTimeStamp(now.Value);
        }

        public static string GetString(this DateTime time)
        {
            return time.ToString("yyyy/MM/dd HH:mm:ss");
        }

        public static string GetString(this DateTime time, string format)
        {
            if (format.IsBlank())
            {
                return GetString(time);
            }
            try
            {
                return time.ToString(format);
            }
            catch
            {
                return GetString(time);
            }
        }

        public static string GetDate(this DateTime time)
        {
            return GetString(time, "yyyy/MM/dd");
        }

        public static string GetDate(this DateTime time, string format)
        {
            if (format.IsBlank())
            {
                return GetDate(time.Date);
            }
            try
            {
                return time.Date.ToString(format);
            }
            catch
            {
                return GetDate(time.Date);
            }
        }

        public static string GetTime(this DateTime time)
        {
            return GetString(time, "HH:mm:ss");
        }

        public static string GetTime(this TimeSpan time)
        {
            return time.ToString(@"hh\:mm\:ss");
        }

        public static string GetTime(this DateTime time, string format)
        {
            if (format.IsBlank())
            {
                return GetTime(time.TimeOfDay);
            }
            try
            {
                return time.ToString(format);
            }
            catch
            {
                return GetTime(time);
            }
        }

        public static string GetString(this DateTime? time)
        {
            if (time == null)
            {
                return string.Empty;
            }
            return time.Value.ToString("yyyy/MM/dd HH:mm:ss");
        }

        public static string GetString(this DateTime? time, string format)
        {
            if (time == null)
            {
                return string.Empty;
            }
            if (format.IsBlank())
            {
                return GetString(time);
            }
            try
            {
                return time.Value.ToString(format);
            }
            catch
            {
                return GetString(time);
            }
        }

        public static string GetDate(this DateTime? time)
        {
            if (time == null)
            {
                return string.Empty;
            }
            return GetString(time, "yyyy/MM/dd");
        }

        public static string GetDate(this DateTime? time, string format)
        {
            if (time == null)
            {
                return string.Empty;
            }
            if (format.IsBlank())
            {
                return GetDate(time.Value.Date);
            }
            try
            {
                return time.Value.Date.ToString(format);
            }
            catch
            {
                return GetDate(time.Value.Date);
            }
        }

        public static string GetTime(this DateTime? time)
        {
            if (time == null)
            {
                return string.Empty;
            }
            return GetString(time, "HH:mm:ss");
        }

        public static string GetTime(this TimeSpan? time)
        {
            if (time == null)
            {
                return string.Empty;
            }
            return time.Value.ToString(@"hh\:mm\:ss");
        }

        public static string GetTime(this DateTime? time, string format)
        {
            if (time == null)
            {
                return string.Empty;
            }
            if (format.IsBlank())
            {
                return GetTime(time.Value.TimeOfDay);
            }
            try
            {
                return time.Value.ToString(format);
            }
            catch
            {
                return GetTime(time);
            }
        }

        public static bool IsBlank(this DateTime? time)
        {
            if (time.HasValue)
            {
                return false;
            }
            return true;
        }

        public static void IsBlank(this DateTime? time, Action action)
        {
            if (time.HasValue)
            {
                return;
            }
            action?.Invoke();
        }

        public static bool IsNotBlank(this DateTime? time)
        {
            return !IsBlank(time);
        }

        public static void IsNotBlank(this DateTime? time, Action action)
        {
            if (IsBlank(time))
            {
                return;
            }
            action?.Invoke();
        }

        /// <summary>
        /// 获取一天的开始时间
        /// </summary>
        /// <param name="time"></param>
        /// <returns></returns>
        public static DateTime? BeginOfDay(this DateTime? time)
        {
            if (time == null)
            {
                return null;
            }
            return BeginOfDay(time.Value);
        }

        /// <summary>
        /// 获取一天的开始时间
        /// </summary>
        /// <param name="time"></param>
        /// <returns></returns>
        public static DateTime BeginOfDay(this DateTime time)
        {
            return new DateTime(time.Year, time.Month, time.Day);
        }

        /// <summary>
        /// 获取一天的结束时间
        /// </summary>
        /// <param name="time"></param>
        /// <returns></returns>
        public static DateTime? EndOfDay(this DateTime? time)
        {
            if (time == null)
            {
                return null;
            }
            return EndOfDay(time.Value);
        }

        /// <summary>
        /// 获取一天的结束时间
        /// </summary>
        /// <param name="time"></param>
        /// <returns></returns>
        public static DateTime EndOfDay(this DateTime time)
        {
            return new DateTime(time.Year, time.Month, time.Day, 23, 59, 59, 999);
        }

        /// <summary>
        /// 获取月份的开始时间
        /// </summary>
        /// <param name="time"></param>
        /// <returns></returns>
        public static DateTime? BeginOfMonth(this DateTime? time)
        {
            if (time == null)
            {
                return null;
            }
            return BeginOfMonth(time.Value);
        }

        /// <summary>
        /// 获取月份的开始时间
        /// </summary>
        /// <param name="time"></param>
        /// <returns></returns>
        public static DateTime BeginOfMonth(this DateTime time)
        {
            return new DateTime(time.Year, time.Month, 1);
        }

        /// <summary>
        /// 获取月份的结束时间
        /// </summary>
        /// <param name="time"></param>
        /// <returns></returns>
        public static DateTime? EndOfMonth(this DateTime? time)
        {
            if (time == null)
            {
                return null;
            }
            return EndOfMonth(time.Value);
        }

        /// <summary>
        /// 获取月份的结束时间
        /// </summary>
        /// <param name="time"></param>
        /// <returns></returns>
        public static DateTime EndOfMonth(this DateTime time)
        {
            int days = DateTime.DaysInMonth(time.Year, time.Month);
            return new DateTime(time.Year, time.Month, days, 23, 59, 59, 999);
        }

        /// <summary>
        /// 获取当前周的开始时间
        /// </summary>
        /// <param name="time"></param>
        /// <returns></returns>
        public static DateTime? BeginOfWeek(this DateTime? time)
        {
            if (time == null)
            {
                return null;
            }
            return BeginOfWeek(time.Value);
        }

        /// <summary>
        /// 获取当前周的开始时间
        /// </summary>
        /// <param name="time"></param>
        /// <returns></returns>
        public static DateTime BeginOfWeek(this DateTime time)
        {
            DateTime dayDate = time.Date;
            switch (time.DayOfWeek)
            {
                case DayOfWeek.Tuesday: 
                    return dayDate.AddDays(-1);
                case DayOfWeek.Wednesday: 
                    return dayDate.AddDays(-2);
                case DayOfWeek.Thursday: 
                    return dayDate.AddDays(-3);
                case DayOfWeek.Friday: 
                    return dayDate.AddDays(-4);
                case DayOfWeek.Saturday: 
                    return dayDate.AddDays(-5);
                case DayOfWeek.Sunday: 
                    return dayDate.AddDays(-6);
                default:
                    return dayDate;
            }
        }

        /// <summary>
        /// 获取当前周的结束时间
        /// </summary>
        /// <param name="time"></param>
        /// <returns></returns>
        public static DateTime? EndOfWeek(this DateTime? time)
        {
            if (time == null)
            {
                return null;
            }
            return EndOfWeek(time.Value);
        }

        /// <summary>
        /// 获取当前周的结束时间
        /// </summary>
        /// <param name="time"></param>
        /// <returns></returns>
        public static DateTime EndOfWeek(this DateTime time)
        {
            DateTime dayDate = EndOfDay(time);
            switch (time.DayOfWeek)
            {
                case DayOfWeek.Monday:
                    return dayDate.AddDays(6);
                case DayOfWeek.Tuesday:
                    return dayDate.AddDays(5);
                case DayOfWeek.Wednesday:
                    return dayDate.AddDays(4);
                case DayOfWeek.Thursday:
                    return dayDate.AddDays(3);
                case DayOfWeek.Friday:
                    return dayDate.AddDays(2);
                case DayOfWeek.Saturday:
                    return dayDate.AddDays(1);
                default:
                    return dayDate;
            }
        }

        public static long Between(this DateTime? time, DateTime? time2, DateCalcUnit calcUnit)
        {
            if (time == null || time2 == null)
            {
                return 0;
            }
            return Between(time.Value, time2.Value, calcUnit);
        }

        public static long Between(this DateTime time, DateTime? time2, DateCalcUnit calcUnit)
        {
            if (time2 == null)
            {
                return 0;
            }
            return Between(time, time2.Value, calcUnit);
        }

        public static long Between(this DateTime? time, DateTime time2, DateCalcUnit calcUnit)
        {
            if (time == null)
            {
                return 0;
            }
            return Between(time.Value, time2, calcUnit);
        }

        public static long Between(this DateTime time, DateTime time2, DateCalcUnit calcUnit)
        {
            TimeSpan span;
            long yearD;
            long monthD;
            if (time > time2)
            {
                span = time - time2;
                yearD = time.Year - time2.Year;
                monthD = yearD * 12 + (time.Month - time2.Month);
            }
            else if(time < time2)
            {
                span = time2 - time;
                yearD = time2.Year - time.Year;
                monthD = yearD * 12 + (time2.Month - time.Month);
            }
            else
            {
                return 0;
            }
            switch (calcUnit)
            {
                case DateCalcUnit.年:
                    return yearD;
                case DateCalcUnit.月:
                    return monthD;
                case DateCalcUnit.日:
                    return (long)span.TotalDays;
                case DateCalcUnit.时:
                    return (long)span.TotalHours;
                case DateCalcUnit.分:
                    return (long)span.TotalMinutes;
                case DateCalcUnit.秒:
                    return (long)span.TotalSeconds;
                case DateCalcUnit.毫秒:
                    return (long)span.TotalMilliseconds;
                default:
                    return 0;
            }
        }
    }
}
